package cn.jhz.learn.blog.util;

import cn.jhz.learn.blog.common.bean.SimpleEntry;
import cn.jhz.learn.blog.component.GLOBAL;

import com.google.common.collect.Lists;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.consumer.*;
import org.jose4j.lang.JoseException;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

public class JwtUtil {

    private final RsaJsonWebKey rsaJsonWebKey;

    private JwtUtil() {
        try {
            /*生成一个封装在JWK中的RSA密钥对，用于JWT的签名和验证*/
            rsaJsonWebKey = RsaJwkGenerator.generateJwk(GLOBAL.TOKEN_INFO.RSA_JSON_WEB_KEY_BITS);
            rsaJsonWebKey.setKeyId(GLOBAL.TOKEN_INFO.RSA_JSON_WEB_KEY_ID); // 给JWK一个密钥ID (kid)，这是最优雅的做法
        } catch (JoseException e) {
            throw new RuntimeException(e);
        }
    }

    private static final class JwtUtilHolder{
        private static final JwtUtil instance = new JwtUtil();
    }

    public static JwtUtil getInstance(){
        return JwtUtilHolder.instance;
    }

    /**
     * 此方法创建一个请求体
     * @param audience
     * @param subject
     * @param claimList
     * @param stringListClaimList
     * @return
     */
    public JwtClaims createJwtClaims(String audience, String subject, List<SimpleEntry<String, Object>> claimList,
            List<SimpleEntry<String, List<String>>> stringListClaimList){

        JwtClaims claims = new JwtClaims(); // 创建声明，这是JWT的content
        claims.setIssuer(GLOBAL.TOKEN_INFO.ISSUER);  // 令牌创建者的签名
        claims.setAudience(audience); // 令牌接收者的签名
        claims.setExpirationTimeMinutesInTheFuture(GLOBAL.TOKEN_INFO.EXPIRATION_TIME_MINUTES_IN_THE_FUTURE); // 令牌到期时间(min)10
        claims.setGeneratedJwtId(); // 令牌的唯一标识符
        claims.setIssuedAtToNow();  // 令牌发出/创建的时间(now)
        claims.setNotBeforeMinutesInThePast(GLOBAL.TOKEN_INFO.NOT_BEFORE_MINUTES_IN_THE_PAST); // 令牌开始生效时间(min)2
        claims.setSubject(subject); // 主题/主体是令牌所针对的对象

        Optional.ofNullable(claimList)
                .orElseGet(Collections::emptyList)
                .forEach(e ->{
                    claims.setClaim(e.getKey(), e.getValue()); // 添加关于主题的附加声明/属性
                });

        Optional.ofNullable(stringListClaimList)
                .orElseGet(Collections::emptyList)
                .forEach(e ->{
                    claims.setStringListClaim(e.getKey(), e.getValue()); // 添加关于主题的组(多值)附加声明/属性,并封装为一个JSON数组
                });

        return claims;
    }

    /**
     * 此方法生成一个JsonWebSignature(JWS)对象,
     * 如果想要对其进行加密，只需将此jwt设置为JsonWebEncryption对象的有效负载，并将cty(内容类型)报头设置为"jwt"
     * <br><br>
     * JWT是JWS或JWE的统称，使用JSON声明作为有效负载<br>
     * 它是一个由三个点('.')分隔的字符串<br>
     * base64url-encoded的格式: Header.Payload.Signature
     * @return
     * @throws JoseException
     */
    public String produceJwsToken(JwtClaims claims) throws JoseException {
        JsonWebSignature jws = new JsonWebSignature(); //声明一个JWS

        jws.setPayload(claims.toJson()); // JWS的payload是JwtClaims的JSON序列
        jws.setKey(rsaJsonWebKey.getPrivateKey()); //JWT签署私钥
        jws.setKeyIdHeaderValue(rsaJsonWebKey.getKeyId()); // 设置Key ID (kid)报头，因为这是优雅的做法
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); // 在JWT/JWS上设置签名算法，这样可以保护索赔的完整性

        return jws.getCompactSerialization(); // 生成紧凑的序列化或完整的JWT/JWS
    }

    /**
     * 使用JwtConsumerBuilder构造一个适当的JwtConsumer，它将用于验证和处理JWT。
     * JWT的特定验证需求依赖于上下文，但是，通常建议要求(合理的)过期时间、可信的发行者和确认系统为预期接收者的受众。
     * 如果JWT也是加密的，您只需向构建器提供一个解密密钥或解密密钥解析器。
     * 如果JWT加密,您只需要提供一个解密密钥或解密密钥解析器生成器。
     * @param jwt
     * @throws JoseException
     * @throws MalformedClaimException
     */
    public boolean validated(String jwt, List<String> aud) throws JoseException, MalformedClaimException,
            InvalidJwtException {
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // JWT必须有过期时间
                .setAllowedClockSkewInSeconds(GLOBAL.TOKEN_INFO.ALLOWED_CLOCK_SKEW_IN_SECONDS) // 允许时间偏差(ms)
                .setRequireSubject() // JWT必须有一个主题
                .setExpectedIssuer(GLOBAL.TOKEN_INFO.ISSUER) // JWT预期的发行者
                .setExpectedAudience(aud.toArray(new String[0])) // JWT预期的接收者
                .setVerificationKey(rsaJsonWebKey.getKey()) // 验证签名的公共key
                .setJwsAlgorithmConstraints(
                        AlgorithmConstraints.ConstraintType.WHITELIST , //只允许在给定上下文中使用预期的签名算法
                        AlgorithmIdentifiers.RSA_USING_SHA256) // 算法签名RS256
                .build(); // 创建JwtConsumer实例

            jwtConsumer.processToClaims(jwt); //  验证JWT
        return true;
    }


    public JwtClaims parseJWT(String jwt) throws InvalidJwtException {
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setSkipSignatureVerification()
                .setSkipDefaultAudienceValidation()
                .build(); // 创建JwtConsumer实例
        return jwtConsumer.processToClaims(jwt);
    }

    /**
     * 更新token有效时间
     * @param jwt
     * @return
     * @throws JoseException
     * @throws InvalidJwtException
     */
    public String reflashJwsToken(String jwt) throws JoseException, InvalidJwtException {
        JwtClaims claims = parseJWT(jwt); //  验证JWT

        claims.setNotBeforeMinutesInThePast(GLOBAL.TOKEN_INFO.NOT_BEFORE_MINUTES_IN_THE_PAST); // 令牌开始生效时间(min)2
        claims.setExpirationTimeMinutesInTheFuture(GLOBAL.TOKEN_INFO.EXPIRATION_TIME_MINUTES_IN_THE_FUTURE); // 令牌到期时间(min)10
        claims.setIssuedAtToNow();  // 令牌发出/创建的时间(now)

        return produceJwsToken(claims);
    }

    public Integer getUid(String jwt) throws InvalidJwtException, MalformedClaimException {
        return Integer.parseInt(parseJWT(jwt).getAudience().get(0));
    }

    public static void main(String[] args) throws JoseException, InvalidJwtException, MalformedClaimException {
        String jwt = JwtUtil.getInstance().produceJwsToken(JwtUtil.getInstance().createJwtClaims("123", "test", null,
                null));

        System.out.println(JwtUtil.getInstance().parseJWT(jwt).getAudience());
//        System.out.println(JwtUtil.getInstance().parseJWT(JwtUtil.getInstance().reflashJwsToken(jwt)));
    }

//    public static Optional<String> produceJweToken() throws JoseException {
        //        // Generate an EC key pair, which will be used for signing and verification of the JWT, wrapped in a JWK
        //        EllipticCurveJsonWebKey senderJwk = EcJwkGenerator.generateJwk(EllipticCurves.P256);
        //
        //        // Give the JWK a Key ID (kid), which is just the polite thing to do
        //        senderJwk.setKeyId("sender's key");
        //
        //
        //        // Generate an EC key pair, wrapped in a JWK, which will be used for encryption and decryption of the JWT
        //        EllipticCurveJsonWebKey receiverJwk = EcJwkGenerator.generateJwk(EllipticCurves.P256);
        //
        //        // Give the JWK a Key ID (kid), which is just the polite thing to do
        //        receiverJwk.setKeyId("receiver's key");
        //
        //        // Create the Claims, which will be the content of the JWT
        //        JwtClaims claims = new JwtClaims();
        //        claims.setIssuer("sender");  // who creates the token and signs it
        //        claims.setAudience("receiver"); // to whom the token is intended to be sent
        //        claims.setExpirationTimeMinutesInTheFuture(10); // time when the token will expire (10 minutes from now)
        //        claims.setGeneratedJwtId(); // a unique identifier for the token
        //        claims.setIssuedAtToNow();  // when the token was issued/created (now)
        //        claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago)
        //        claims.setSubject("subject"); // the subject/principal is whom the token is about
        //        claims.setClaim("email","mail@example.com"); // additional claims/attributes about the subject can be added
        //        List<String> groups = Arrays.asList("group-1", "other-group", "group-3");
        //        claims.setStringListClaim("groups", groups); // multi-valued claims work too and will end up as a JSON array
        //
        //        // A JWT is a JWS and/or a JWE with JSON claims as the payload.
        //        // In this example it is a JWS nested inside a JWE
        //        // So we first create a JsonWebSignature object.
        //        JsonWebSignature jws = new JsonWebSignature();
        //
        //        // The payload of the JWS is JSON content of the JWT Claims
        //        jws.setPayload(claims.toJson());
        //
        //        // The JWT is signed using the sender's private key
        //        jws.setKey(senderJwk.getPrivateKey());
        //
        //        // Set the Key ID (kid) header because it's just the polite thing to do.
        //        // We only have one signing key in this example but a using a Key ID helps
        //        // facilitate a smooth key rollover process
        //        jws.setKeyIdHeaderValue(senderJwk.getKeyId());
        //
        //        // Set the signature algorithm on the JWT/JWS that will integrity protect the claims
        //        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256);
        //
        //        // Sign the JWS and produce the compact serialization, which will be the inner JWT/JWS
        //        // representation, which is a string consisting of three dot ('.') separated
        //        // base64url-encoded parts in the form Header.Payload.Signature
        //        String innerJwt = jws.getCompactSerialization();
        //
        //        // The outer JWT is a JWE
        //        JsonWebEncryption jwe = new JsonWebEncryption();
        //
        //        // The output of the ECDH-ES key agreement will encrypt a randomly generated content encryption key
        //        jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.ECDH_ES_A128KW);
        //
        //        // The content encryption key is used to encrypt the payload
        //        // with a composite AES-CBC / HMAC SHA2 encryption algorithm
        //        String encAlg = ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256;
        //        jwe.setEncryptionMethodHeaderParameter(encAlg);
        //
        //        // We encrypt to the receiver using their public key
        //        jwe.setKey(receiverJwk.getPublicKey());
        //        jwe.setKeyIdHeaderValue(receiverJwk.getKeyId());
        //
        //        // A nested JWT requires that the cty (Content Type) header be set to "JWT" in the outer JWT
        //        jwe.setContentTypeHeaderValue("JWT");
        //
        //        // The inner JWT is the payload of the outer JWT
        //        jwe.setPayload(innerJwt);
        //
        //        // Produce the JWE compact serialization, which is the complete JWT/JWE representation,
        //        // which is a string consisting of five dot ('.') separated
        //        // base64url-encoded parts in the form Header.EncryptedKey.IV.Ciphertext.AuthenticationTag
        //        String jwt = jwe.getCompactSerialization();
        //
        //
        //        // Now you can do something with the JWT. Like send it to some other party
        //        // over the clouds and through the interwebs.
        //        System.out.println("JWT: " + jwt);
        //        return Optional.of(jwt);
        //    }
}
